﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using API;

namespace mb
{
    public partial class CacheStream
    {
        class UrlDataSegment
        {
            enum Status
            {
                None,
                Loading,
                Memory,
                Disk,
            }
            // public const int SegmentSize = 1600;
            public const int SegmentSize = 128 * 1024;

            byte[] data;
            Status status;
            CacheStream owner;
            int index;
            bool hasRead;

            public UrlDataSegment(CacheStream stream, int index)
            {
                this.owner = stream;
                this.index = index;
                status = Status.None;
                hasRead = false;
            }

            public bool IsAvailable()
            {
                return status == Status.Memory;
            }

            public async void Preload()
            {
                if (status == Status.Loading) return;
                if (status == Status.Memory) return;

                try
                {
                    status = Status.Loading;
                    var start = index * SegmentSize;
                    // logger.Log("preloading start", index);
                    var res = await owner.RequestData(start, SegmentSize);
                    using(var contentStream = await res.Content.ReadAsStreamAsync())
                    {
                        data = new byte[contentStream.Length];
                        var bytesRead = await contentStream.ReadAsync(data, 0, data.Length);
                        if (bytesRead != data.Length)
                            throw new System.Exception("preload stream length error");
                    }
                    // logger.Log("preloading finish", index);
                    status = Status.Memory;
                }
                catch (Exception e)
                {
                    status = Status.None;
                    logger.Log("preloading failed", index);
                }
            }

            void Load()
            {
                var start = index * SegmentSize;
                var res = owner.RequestData(start, SegmentSize);
                if (res == null)
                {
                    throw new System.Exception($"download data segment failed1:{index}, {start}");
                }

                data = res.Result.Content.ReadAsByteArrayAsync().Result;
                if (data.Length != SegmentSize)
                {
                    // throw new System.Exception($"download data segment failed2:{index}, {start}");
                }

                // string hstr = "";
                // for (int i = 0; i < 10; i++) hstr += data[i].ToString("x");
                // logger.Log($"download data seg {index}/{start}-{start+SegmentSize-1},  head[{hstr}]");
                // foreach (var key in res.Content.Headers)
                // {
                //     logger.Log(key.Key, string.Join(",", key.Value));
                // }

                status = Status.Memory;
            }

            public int CopyTo(long from, byte[] to, int offset, int size)
            {
                if (status == Status.None)
                {
                    // logger.Log("load segment", this.index);
                    Load();
                }

                while (status == Status.Loading)
                {
                    // logger.Log("wait for preloading", this.index, this.status);
                    System.Threading.Thread.Sleep(1000);

                    //流已经关闭了
                    if (owner.closed)
                    {
                        // logger.Log("found stream closed, cancel CopyTo", this.index, this.status);
                        return 0;
                    }
                }

                if (status != Status.Memory)
                    return 0;

                if (!hasRead)
                {
                    hasRead = true;
                    // logger.Log("read segment", this.index);
                }

                if (size > data.Length)
                    size = data.Length;

                // logger.Log($"seg.{index} copy:{from},{size} => {offset}");
                System.Array.Copy(data, from, to, offset, size);

                return size;
            }
        }
    }

    public partial class CacheStream : IDataStream
    {
        static ILog logger = ServiceLocator.Instance.Get<ILog>();

        public string url;
        public string cachePath;
        long totalLength;
        HttpClient httpClient;
        FileStream fileStream;

        UrlDataSegment[] dataSegments;
        int SegmentSize;
        protected bool closed;

        public CacheStream(string url, string cachePath)
        {
            this.url = url;
            this.cachePath = cachePath;
            httpClient = new HttpClient();
            fileStream = new FileStream(cachePath, FileMode.OpenOrCreate, FileAccess.ReadWrite);
            SegmentSize = UrlDataSegment.SegmentSize;
            closed = false;
            // Init();
        }

        void Init()
        {
            //构造时请求总长度
            var res = RequestData(0, SegmentSize).Result;
            totalLength = (long) res.Content.Headers.ContentRange.Length;

            var(s1, s2) = MapRangeToSegments(0, totalLength);
            dataSegments = new UrlDataSegment[s2 + 1];
            for (var i = 0; i <= s2; i++)
                dataSegments[i] = new UrlDataSegment(this, i);

            logger.Log($"res length: {totalLength}, seg:{s2+1}, {url}");
        }

        Task<HttpResponseMessage> RequestData(long from, long length)
        {
            var request = new HttpRequestMessage
            {
                RequestUri = new System.Uri(url + $"?from={from}"),
                Method = HttpMethod.Get,
            };
            request.Headers.Add("range", $"bytes={from}-{from+length-1}");

            try
            {
                var response = httpClient.SendAsync(request);
                return response;
            }
            catch (System.Exception e)
            {
                logger.Log("request movie fail", url);
                if (totalLength == 0)
                    throw e;
                return null;
            }
        }

        /*
        字节数映射到段数
        如段长为1600，那么0+2048将映射为0-1
        */
        (int, int) MapRangeToSegments(long from, long size)
        {
            int s1 = (int) (from / SegmentSize);
            int s2 = (int) ((from + size - 1) / SegmentSize);
            return (s1, s2);
        }
        public long GetAvailableLength()
        {
            // for (int i = dataSegments.Length - 1; i >= 0; i--)
            for (int i = 0; i < dataSegments.Length; i++)
            {
                if (!dataSegments[i].IsAvailable())
                {
                    // logger.Log("avail seg", i);
                    return (i) * SegmentSize;
                }
            }
            return 0;
        }

        public int Read(long from, byte[] to, int offset, int size)
        {
            if (totalLength == 0) Init();

            int fixedSize = size;
            if (from + size > totalLength)
                fixedSize = (int) (totalLength - from);

            var(s1, s2) = MapRangeToSegments(from, fixedSize);
            // logger.Log($"{this.GetHashCode().ToString("X")} read {from}-{from+fixedSize-1} => {offset}, seg:{s1}-{s2}");

            var totalRead = 0;
            var lastRead = 0;
            for (int k = s1; k <= s2; k++, offset += lastRead)
            {
                //第一段的起点要计算，其它都是从段头读起
                var bfrom = k == s1?from - SegmentSize * k : 0;
                //最后一段的终点要计算，其它都要读完整段
                var bend = k == s2?(from + fixedSize - 1) - SegmentSize * k : SegmentSize - 1;

                // logger.Log($"seg.{k}, {bfrom},{bend}");
                lastRead = dataSegments[k].CopyTo(bfrom, to, offset, (int) (bend - bfrom + 1));
                totalRead += lastRead;
            }

            int preload = 3;
            int preloadSeg = s2 + 1;
            while (preload >= 0 && preloadSeg < dataSegments.Length)
            {
                dataSegments[preloadSeg].Preload();
                preload--;
                preloadSeg++;
            }

            return totalRead;
        }
        public void Close()
        {
            fileStream.Close();
            httpClient.CancelPendingRequests();
            closed = true;
        }

        public long GetLength() => totalLength;
        public List < (int start, int end) > GetLocalSegCount()
        {
            return null;
        }
    }

    public class CacheStreamManger : ICacheDataStreamManager
    {
        static ILog logger = ServiceLocator.Instance.Get<ILog>();
        public IDataStream CreateDataStream(string strInfo)
        {
            var url = strInfo;
            var cachePath = Xamarin.Essentials.FileSystem.CacheDirectory + "/movie.cache";
            return new CacheStream(url, cachePath);
        }

    }
}